using System;

namespace LiteGrid.Mvc.ToBeRefactored
{
	/// <summary>
	/// Base class for mapping between view models from liteGrid and 
	/// concrete types. 
	/// </summary>
	/// <typeparam name="TModel">The view model type.</typeparam>
	/// <typeparam name="TTarget">The target type that should be updated from the view model.</typeparam>
	public abstract class LiteGridUpdater<TModel, TTarget>
		where TModel : ILiteGridUpdateModel<TTarget>
		where TTarget : class, new()
	{
		/// <summary>
		/// Applies updates from the view model.
		/// </summary>
		/// <param name="models">The requirement models containing changes.</param>
		/// <returns>The view models that were applied to the requirements.  IDs for new requirements
		/// will be synchronized to the real objects.</returns>
		public TModel[] ApplyUpdates(TModel[] models)
		{
			if (models == null)
			{
				throw new ArgumentNullException("models", "No models were received by the server.  Please try again.");
			}

			//First, apply any updates and moves. Note that because liteGrid sends things in order,
			//we are guaranteed to add a new object before we'll process anything that was added as a child
			//to the new object.
			foreach (var updatedModel in models)
			{
				BeforeUpdateModel(updatedModel);

				//Get the existing object or create a new one.
				TTarget target = GetTarget(updatedModel);

				if (target != null)
				{
					updatedModel.MapTo(target);
				}
				else
				{
					CreateAndUpdate(updatedModel);
				}

				//Apply any moves
				if (updatedModel is ILiteGridMovableModel<TTarget>
					&& ((ILiteGridMovableModel<TTarget>)updatedModel).NewParentId.HasValue)
				{
					MoveToNewParent(updatedModel);
				}
			}

			//Now apply any deletes.
			foreach (var updateModel in models)
			{
				if (updateModel.Deleted == true)
				{
					DoDelete(updateModel, GetTarget(updateModel));
				}
			}

			return models;
		}

		/// <summary>
		/// Moves the object to a new parent.
		/// </summary>
		/// <param name="model"></param>
		protected virtual void MoveToNewParent(TModel model)
		{
			throw new NotImplementedException("Moving items to a new parent is not supported.");
		}

		/// <summary>
		/// Derived classes can implement this if they want to support
		/// adding new items from liteGrid.  Implementors must perform
		/// three actions: create a new TTarget; apply the updates
		/// from model to the new TTarget; synchronize the ID of the
		/// new TTarget with the model.
		/// </summary>
		/// <param name="model"></param>
		/// <returns></returns>
		protected virtual TTarget CreateAndUpdate(TModel model)
		{
			throw new NotImplementedException("Adding new items is not supported.");
		}

		/// <summary>
		/// Derived classes can implement this if they want to support deletes.
		/// If implemented, this should delete the specified target.
		/// </summary>
		/// <param name="model">The view model.</param>
		/// <param name="target">The object that should be deleted.</param>
		protected virtual void DoDelete(TModel model, TTarget target)
		{
			throw new NotImplementedException("Deleting items is not supported.");
		}

		/// <summary>
		/// Called before a model is updated.  You can transform the 
		/// model by overriding this method.
		/// </summary>
		/// <param name="model"></param>
		protected virtual void BeforeUpdateModel(TModel model)
		{
		}

		/// <summary>
		/// Implemented by derived classes, used to find the specific object
		/// that should be updated. 
		/// </summary>
		/// <param name="model"></param>
		/// <returns></returns>
		protected abstract TTarget GetTarget(TModel model);
	}
}